package com.flyqiu.flow.model.format.flowable;

import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.JSONReader;
import com.flyqiu.common.tools.StringTools;
import com.flyqiu.flow.api.format.IBpmnFormat;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class FlyQiuFlowFlowableBpmnFormat implements IBpmnFormat {

    public static final String BPMN_XML = "bpmn";
    private static final Map<String, DefaultFlowableElementFormat> elementFormatMapper = new HashMap<>();
    private static DefaultFlowableElementFormat defaultFormat = new DefaultFlowableElementFormat();
    private static OutputFormat xmlFormat = OutputFormat.createCompactFormat();

    static {
        addFormatMapper(new FlowableDiagramFormat());
        addFormatMapper(new FlowableSubProcessFormat());
        addFormatMapper(new FlowableProcessFormat());
        addFormatMapper(new FlowableUserTaskFormat());
        addFormatMapper(new FlowableSequenceFlowFormat());
    }

    public static void setXmlFormat(OutputFormat xmlFormat) {
        FlyQiuFlowFlowableBpmnFormat.xmlFormat = xmlFormat;
    }

    public static void addFormatMapper(DefaultFlowableElementFormat format) {
        if (format == null) {
            throw new RuntimeException("format cannot be null");
        }
        if (StringTools.isAllBlank(format.xmlElementType())) {
            throw new RuntimeException("xmlElementType cannot be null or empty");
        }
        if (StringTools.isAllBlank(format.jsonElementType())) {
            throw new RuntimeException("jsonElementType cannot be null or empty");
        }
        elementFormatMapper.put(format.xmlElementType(), format);
        elementFormatMapper.put(format.jsonElementType(), format);


    }

    public static void setDefaultFormat(DefaultFlowableElementFormat format) {
        if (null == format) {
            throw new RuntimeException("format cannot be null");
        }
        defaultFormat = format;
    }

    public static boolean hasElementTypeFormat(String elementType) {
        return elementFormatMapper.containsKey(elementType);
    }

    @Override
    public String formatType() {
        return BPMN_XML;
    }

    @Override
    public JSONObject convertStartBpmn(byte[] bytes) {
        SAXReader reader = new SAXReader();
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
        Document document = null;
        try {
            document = reader.read(byteArrayInputStream);
        } catch (DocumentException e) {
            throw new RuntimeException(e);
        }
        Element rootElement = document.getRootElement();
        List<Element> process = rootElement.elements("process");
        JSONArray processArray = new JSONArray();
        for (Element processItem : process) {
            DefaultFlowableElementFormat elementFormat = getElementFormat("process");
            JSONObject item = elementFormat.elementToJson(this, processItem);
            processArray.add(item);
        }
        List<Element> diagramElements = rootElement.elements("BPMNDiagram");
        JSONArray diagrams = new JSONArray();
        for (Element diagramElement : diagramElements) {
            DefaultFlowableElementFormat elementFormat = getElementFormat("BPMNDiagram");
            JSONObject diagram = elementFormat.elementToJson(this, diagramElement);
            diagrams.add(diagram);
        }
        JSONObject result = new JSONObject();
        JSONObject processObject = processArray.getJSONObject(0);
        result.put("defKey", processObject.getString("defKey"));
        result.put("defName", processObject.getString("defName"));
        result.put("process", processArray);
        result.put("diagram", diagrams);
        return result;
    }

    @Override
    public byte[] convertTargetFormatByte(byte[] jsonStrBytes) {
        Document document = DocumentHelper.createDocument();
        Element root = document.addElement("bpmn2:definitions");
        root.addNamespace("bpmn", "http://www.w3.org/2001/XMLSchema-instance");
        root.addNamespace("xmlns", "http://www.omg.org/spec/BPMN/20100524/MODEL");
        root.addNamespace("bpmn2", "http://www.omg.org/spec/BPMN/20100524/MODEL");
        root.addNamespace("bpmndi", "http://www.omg.org/spec/BPMN/20100524/DI");
        root.addNamespace("dc", "http://www.omg.org/spec/DD/20100524/DC");
        root.addNamespace("di", "http://www.omg.org/spec/DD/20100524/DI");
        root.addNamespace("flowable", "http://flowable.org/bpmn");
        root.addAttribute("targetNamespace", "http://bpmn.io/schema/bpmn");
        root.addAttribute("exporter", "bpmn-js (https://demo.bpmn.io)");
        root.addAttribute("exporterVersion", "12.0.0");
        JSONReader reader = JSONReader.of(jsonStrBytes);
        JSONObject bpmn = reader.readJSONObject();

        JSONArray processArray = bpmn.getJSONArray("process");
        for (int i = 0; i < processArray.size(); i++) {
            JSONObject process = processArray.getJSONObject(i);
            getElementFormat("process").jsonToElement(this, process, root);
        }
        JSONArray diagramArray = bpmn.getJSONArray("diagram");

        for (int i = 0; i < diagramArray.size(); i++) {
            JSONObject diagram = diagramArray.getJSONObject(i);
            getElementFormat("BPMNDiagram").jsonToElement(this, diagram, root);
        }
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        byte[] array;

        try {
            XMLWriter xmlWriter = new XMLWriter(outputStream, xmlFormat);
            xmlWriter.write(document);
            xmlWriter.flush();
            xmlWriter.close();
            array = outputStream.toByteArray();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return array;
    }


    protected DefaultFlowableElementFormat getElementFormat(String elementType) {
        if (hasElementTypeFormat(elementType)) {
            return elementFormatMapper.get(elementType);
        }
        return defaultFormat;
    }


}
